package org.aerie.forest.core.frame.init;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;

import org.aerie.forest.core.brick.exception.ExceptionPack;
import org.aerie.forest.core.brick.exception.custom.MissForestLogConfigFileException;
import org.aerie.forest.core.brick.log.GlobalLogger;
import org.aerie.forest.core.frame.GlobalPath;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.Context;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.StatusPrinter;

/**
 * @description forest日志初始化配置器
 *
 * @author quark
 * @organization aerie
 * @date 2020年2月27日 下午5:15:00
 * @version 1.2.0
 */
enum ForestLogInit {

	/**
	 * @description 单例
	 * 
	 * @author quark
	 * @date 2020年2月27日 下午5:15:14
	 * @version 1.2.0
	 */
	INSTANCE;

	/**
	 * @description logger
	 * 
	 * @author quark
	 * @date 2020-10-16 13:36:14
	 * @version 2.0.1
	 */
	private Logger logger;

	/**
	 * @description 日志绑定实现的类型
	 *
	 * @author quark
	 * @organization aerie
	 * @date 2020年2月27日 下午5:15:28
	 * @version 1.2.0
	 */
	private enum LogBinderType {

		/**
		 * @description log4j2
		 * 
		 * @author quark
		 * @date 2020年2月27日 下午5:15:36
		 * @version 1.2.0
		 */
		LOG4J2,

		/**
		 * @description logback
		 * 
		 * @author quark
		 * @date 2020年2月27日 下午5:15:44
		 * @version 1.2.0
		 */
		LOGBACK;

		private LogBinderType() {
			// Do Nothing
		}
	}

	/**
	 * @description 静态日志绑定器路径
	 * 
	 * @author quark
	 * @date 2020年2月27日 下午5:16:33
	 * @version 1.2.0
	 */
	private static final String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

	/**
	 * Construction
	 * 
	 *
	 * @date 2020年2月27日 下午5:16:48
	 * @version 1.2.0
	 */
	private ForestLogInit() {
		// Do Nothing
	}

	/**
	 * @description 加载log配置环境
	 *
	 * @date 2020年2月27日 下午5:16:55
	 * @version 1.2.0
	 */
	synchronized void loadLogConfig() {
		if (ForestFrameContext.INSTANCE.getLogInitSign()) {
			GlobalLogger.LOGGER.warn("log配置环境已经加载，无法重复加载");
			return;
		}
//		System.err.println("==>\t开始刷新log配置环境");
		try {
			if (getLogBinderType() == LogBinderType.LOG4J2) {
				// 分两种情况一种是LoggerContext已经加载了，一种是还没有加载【这里因为loggerContext初始化，无法加载，所有强制重新加载】
				// 已经加载需要重新加载
				afreshProcessLog4j2ConfigFile();
			} else {
				// 加载logback配置文件
				processLogbackConfigFile();
			}
			outputLogo();
		} catch (Exception e) {
			System.err.println("加载log配置文件失败:\nCaused By:");
			// 加载log之前只能这么打印
			e.printStackTrace();
			System.err.println("强制关闭");
			System.exit(-1);
		}
	}

	/**
	 * @description 启动logo
	 *
	 * @author quark
	 * @date 2020年5月3日 下午3:24:33
	 * @version 1.2.0
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	private void outputLogo() throws Exception {
		InputStream resourceAsStream = ForestLogInit.class.getResourceAsStream(GlobalPath.DEFAULT_LOGO);
		try (InputStreamReader fileReader = new InputStreamReader(resourceAsStream);
				BufferedReader bufferedReader = new BufferedReader(fileReader);) {
			logger = LoggerFactory.getLogger("root");
			String string;
			while ((string = bufferedReader.readLine()) != null) {
				logger.error(string);
			}
			String forestVersion = ForestFrameContext.INSTANCE.getForestVersion();
			logger.error(String.format("%-" + (39 - forestVersion.length()) + "s", " :: Forest ::") + "[ {} ]\n",
					forestVersion);
		}
	}

	/**
	 * @description 判断loggerContext是否已经加载
	 * @return loggerContext加载标识符
	 *
	 * @date 2020年2月27日 下午5:17:46
	 * @version 1.2.0
	 */
	@SuppressWarnings("unused")
	private boolean isLoggerContextLoaded() {
		// 没有想到好办法,现在思路通过其他方法解决
		// TODO
		return true;
	}

	/**
	 * @description 获得日志绑定实现的类型
	 * @return 日志绑定实现的类型
	 * @throws Exception 查找可能的静态记录器绑定路径集异常
	 *
	 * @date 2020年2月27日 下午5:17:59
	 * @version 1.2.0
	 */
	private LogBinderType getLogBinderType() throws Exception {
		// 获得可能的静态日志绑定路径
		List<URL> findPossibleStaticLoggerBinderPath = findPossibleStaticLoggerBinderPathSet();
		int size = findPossibleStaticLoggerBinderPath.size();
		// 静态日志绑定路径有且只能有一个
		if (size != 1) {
			throw new Exception("slf4j检测到" + size + "个log依赖，有且只能有一个");
		}
		String staticLoggerBinderPath = findPossibleStaticLoggerBinderPath.get(0).getPath();
		// 判断依赖的log的类型【暂时只支持log4j2和logback】
		if (staticLoggerBinderPath.indexOf("log4j-slf4j-impl-2") != -1) {
			return LogBinderType.LOG4J2;
		} else if (staticLoggerBinderPath.indexOf("ch/qos/logback/logback-classic/") != -1) {
			return LogBinderType.LOGBACK;
		} else {
			throw new Exception("现在只支持log4j2和logback;而查询到的日志绑定路径是：" + staticLoggerBinderPath);
		}
	}

	/**
	 * @description 重新加载log4j2的配置文件
	 * @throws Exception IO异常
	 *
	 * @date 2020年2月27日 下午5:18:17
	 * @version 1.2.0
	 */
	private void afreshProcessLog4j2ConfigFile() throws Exception {
		// 创建log4j2配置文件
		processLog4j2ConfigFile();
		try (// 获取log4j2配置文件流
				InputStream inputStream = ForestLogInit.class.getResourceAsStream(GlobalPath.LOG4J2_FILE_PATH);) {
			// 防止loggerContext初始化了但是loggerFactory没有初始化的情况【会报没有找到Log4j2配置文件】
			logger = LoggerFactory.getLogger("Logger");
//			System.err.println("==> ↑↑↑无视上方无法找到Log4j2配置文件的错误");
			// 获得配置源
			ConfigurationSource configurationSource = new ConfigurationSource(inputStream);
			// 初始化获得log的上下文
			LoggerContext loggerContext = Configurator.initialize(Thread.currentThread().getContextClassLoader(),
					configurationSource);
			// 得到配置文件的环境
			XmlConfiguration xmlConfiguration = new XmlConfiguration(loggerContext, configurationSource);
			// 讲log的上下文按照指定的配置文件启动
			loggerContext.start(xmlConfiguration);
		}
	}

	/**
	 * @description 加载log4j2配置文件
	 * @throws Exception 读取配置文件的异常
	 *
	 * @date 2020年2月27日 下午5:18:57
	 * @version 1.2.0
	 */
	private void processLog4j2ConfigFile() throws Exception {
//		System.err.println("系统初始化加载log4j2配置文件");
		// 外置log4j2配置文件
		String filePath = GlobalPath.MODULE_PATH + GlobalPath.RESOURCE_PATH;
		File logConfigFile = new File(filePath + GlobalPath.LOG4J2_FILE_PATH);
		if (!logConfigFile.exists()) {
			System.err.println("开始创建log4j2配置文件...:" + filePath);
			try {
				File logConfigFilepack = new File(filePath + GlobalPath.LOG_PATH);
				if (!logConfigFilepack.exists()) {
					// 包路径不存在，创建
					logConfigFilepack.mkdirs();
					System.err.println("创建log4j2配置文件包路径");
				}
				// 创建配置文件
				if (!logConfigFile.createNewFile()) {
					throw new Exception("创建配置文件失败");
				} else {
					System.err.println("创建log4j2配置文件成功");
				}
				// 写入jar包中的配置文件
				try (FileOutputStream fileOutputStream = new FileOutputStream(logConfigFile);
						InputStream inputStream = Optional
								.ofNullable(ForestLogInit.class.getResourceAsStream(GlobalPath.LOG4J2_FILE_PATH))
								.orElseThrow(MissForestLogConfigFileException::new);) {
					byte[] buffer = new byte[1024];
					int num;
					while ((num = inputStream.read(buffer)) != -1) {
						for (int i = 0; i < num; i++)
							fileOutputStream.write(buffer[i]);
					}
				}
				System.err.println("log4j2配置文件写入完成");
			} catch (Exception e) {
				throw new Exception("处理log4j2的配置文件失败", e);
			}
		}
//		System.setProperty("log4j2ConfigurationFile", RESOURCES_PATH + PACK_PATH + LOG4J2_FILE_NAME);
	}

	/**
	 * @description 处理logback的配置文件
	 * @throws Exception 反射异常
	 *
	 * @date 2020年2月27日 下午5:19:17
	 * @version 1.2.0
	 */
	private void processLogbackConfigFile() throws Exception {
		ILoggerFactory iLoggerFactory = org.slf4j.LoggerFactory.getILoggerFactory();
		File file = new File(GlobalPath.MODULE_PATH + GlobalPath.LOGBACK_FILE_PATH);
		Class<?> forName = Class.forName("ch.qos.logback.classic.joran.JoranConfigurator");
		ContextAwareBase newInstance = (ContextAwareBase) forName.newInstance();
		newInstance.setContext((Context) iLoggerFactory);
		Method method = forName.getMethod("doConfigure", File.class);
		method.invoke(newInstance, file);
		StatusPrinter.printInCaseOfErrorsOrWarnings((Context) iLoggerFactory);
	}

	/**
	 * @description 查找可能的静态记录器绑定路径集
	 * @return 能的静态记录器绑定路径集
	 * @throws Exception 读取类的异常
	 *
	 * @date 2020年2月27日 下午5:19:29
	 * @version 1.2.0
	 */
	private List<URL> findPossibleStaticLoggerBinderPathSet() throws Exception {
		List<URL> staticLoggerBinderPathSet = new ArrayList<>();
		try {
			// 加载LoggerFactory
			ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
			Enumeration<URL> paths = null;
			// 搜索路径中查找静态日志绑定器路径的所有资源
			if (loggerFactoryClassLoader == null) {
				paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
			} else {
				paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
			}
			// 遍历并添加到返回集合中
			while (paths.hasMoreElements()) {
				URL path = paths.nextElement();
				staticLoggerBinderPathSet.add(path);
			}
		} catch (IOException e) {
			throw new ExceptionPack("找到可能的静态日志绑定路径出错", e);
		}
		return staticLoggerBinderPathSet;
	}

}
